﻿using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using BingleMaps.BingMaps.Geocode;
using BingleMaps.BingMaps.Imagery;
using BingleMaps.BingMaps.Route;
using BingleMaps.BingMaps.Search;
using BingleMaps.Models;
using BingleMaps.ViewModels;
using Microsoft.Phone.Controls.Maps;
using Microsoft.Phone.Controls.Maps.Platform;
using Confidence = BingleMaps.BingMaps.Geocode.Confidence;
using DeviceType = BingleMaps.BingMaps.Geocode.DeviceType;
using DistanceUnit = BingleMaps.BingMaps.Geocode.DistanceUnit;
using ExecutionOptions = BingleMaps.BingMaps.Geocode.ExecutionOptions;
using GeocodeLocation = BingleMaps.BingMaps.Search.GeocodeLocation;
using GeocodeResult = BingleMaps.BingMaps.Geocode.GeocodeResult;
using SizeOfint = BingleMaps.BingMaps.Imagery.SizeOfint;
using UserLocation = BingleMaps.BingMaps.Search.UserLocation;
using UserProfile = BingleMaps.BingMaps.Geocode.UserProfile;

namespace BingleMaps.Services
{
	public class BingMapsServices
	{
		#region "Static Request Objects"

		private static readonly Credentials _bingMapCredentials = new Credentials { ApplicationId = (string)Application.Current.Resources["BingMapApplicationId"] };
		private static readonly UserLocation _searchCurrentLocation = new UserLocation();
		private static readonly ExecutionOptions _geocodeExecutionOptions = new ExecutionOptions { SuppressFaults = true };

		private static readonly UserProfile _geocodeUserProfile = new UserProfile
		{
			DeviceType = DeviceType.Mobile,
			DistanceUnit = RegionInfo.CurrentRegion.IsMetric ? DistanceUnit.Kilometer : DistanceUnit.Mile
		};

		private static readonly SearchRequest _searchRequest = new SearchRequest
		{
			Credentials = _bingMapCredentials,
			ExecutionOptions = new BingMaps.Search.ExecutionOptions { SuppressFaults = true },
			Culture = CultureInfo.CurrentCulture.Name,
			UserProfile = new BingMaps.Search.UserProfile { DeviceType = BingMaps.Search.DeviceType.Mobile }
		};

		private static readonly ReverseGeocodeRequest _reverseGeocodeRequest = new ReverseGeocodeRequest
		{
			Credentials = _bingMapCredentials,
			Culture = CultureInfo.CurrentCulture.Name,
			ExecutionOptions = _geocodeExecutionOptions,
			UserProfile = _geocodeUserProfile,
			Location = new Location { Altitude = default(double) }
		};

		private static readonly BingMaps.Geocode.UserLocation _geocodeUserLocation = new BingMaps.Geocode.UserLocation();

		private static readonly GeocodeRequest _geocodeRequest = new GeocodeRequest
		{
			Credentials = _bingMapCredentials,
			Culture = CultureInfo.CurrentCulture.Name,
			ExecutionOptions = _geocodeExecutionOptions,
			Options = new GeocodeOptions
			{
				Filters = new ObservableCollection<FilterBase>
				{
					new ConfidenceFilter { MinimumConfidence = Confidence.High }
				}
			},
			UserProfile = _geocodeUserProfile
		};

		private static readonly BingMaps.Route.UserLocation _routeUserLocation = new BingMaps.Route.UserLocation();

		private static readonly RouteRequest _routeRequest = new RouteRequest
		{
			Credentials = _bingMapCredentials,
			Culture = CultureInfo.CurrentUICulture.Name,
			Waypoints = new ObservableCollection<Waypoint>
			{
				new Waypoint { Location = new Location() },
				new Waypoint { Location = new Location() }
			},
			ExecutionOptions = new BingMaps.Route.ExecutionOptions { SuppressFaults = true },
			Options = new RouteOptions { RoutePathType = RoutePathType.Points },
			UserProfile = new BingMaps.Route.UserProfile
			{
				DeviceType = BingMaps.Route.DeviceType.Mobile,
				DistanceUnit = RegionInfo.CurrentRegion.IsMetric ? BingMaps.Route.DistanceUnit.Kilometer : BingMaps.Route.DistanceUnit.Mile
			}
		};

		private static readonly MapUriRequest _mapUriRequest = new MapUriRequest
		{
			Center = new Location(),
			Credentials = _bingMapCredentials,
			Culture = CultureInfo.CurrentCulture.Name,
			ExecutionOptions = new BingMaps.Imagery.ExecutionOptions { SuppressFaults = true },
			Options = new MapUriOptions { Style = MapStyle.Road_v1, ImageSize = new SizeOfint { Height = 125, Width = 125 } },
			UserProfile = new BingMaps.Imagery.UserProfile
			{
				DeviceType = BingMaps.Imagery.DeviceType.Mobile,
				DistanceUnit = RegionInfo.CurrentRegion.IsMetric ? BingMaps.Imagery.DistanceUnit.Kilometer : BingMaps.Imagery.DistanceUnit.Mile
			}
		};

		#endregion


		public static void Search(string query, ShapeBase mapView, Action<ObservableCollection<Location>> callback)
		{
			var c = new SearchServiceClient("BasicHttpBinding_ISearchService");
			c.SearchCompleted += (s, e) =>
			{
				try
				{
					if (e.Result == null)
					{
						callback(null);
						return;
					}
					var locations = new ObservableCollection<Location>();
					foreach (GeocodeLocation l in
						from set in e.Result.ResultSets
						from r in set.Results
						from l in r.LocationData.Locations
						select l)
					{
						locations.Add(l);
					}

					if (locations.Count > 0)
					{
						callback(locations);
						return;
					}
					GetLocations(query, mapView, 1, callback);
				}
				catch
				{
					callback(null);
				}
			};
			_searchRequest.Query = query;
			_searchRequest.UserProfile.MapView = mapView;
			if (ViewModelLocator.MainStatic.CurrentLocation == null || ViewModelLocator.MainStatic.CurrentLocation.IsUnknown)
			{
				_searchRequest.UserProfile.CurrentLocation = null;
			}
			else
			{
				_searchCurrentLocation.Latitude = ViewModelLocator.MainStatic.CurrentLocation.Latitude;
				_searchCurrentLocation.Longitude = ViewModelLocator.MainStatic.CurrentLocation.Longitude;
				_searchRequest.UserProfile.CurrentLocation = _searchCurrentLocation;
			}
			c.SearchAsync(_searchRequest);
		}

		public static void GetDirections(DirectionPoint from, DirectionPoint to, ShapeBase mapView, TravelMode travelMode, Action<RouteResult> callback)
		{
			if (from.Location != null && to.Location != null)
			{
				GetDirections(from.Location, to.Location, mapView, travelMode, callback);
				return;
			}
			if (from.Location == null)
			{
				if (from.SelectedSuggestion == null)
				{
					GetLocations(from.Input, mapView, 0, locations =>
					{
						if (locations == null || locations.Count == 0)
						{
							callback(null);
							return;
						}
						from.Location = locations[0];
						if (to.Location == null)
						{
							return;
						}
						GetDirections(from.Location, to.Location, mapView, travelMode, callback);
						return;
					});
				}
				else
				{
					GoogleMapsServices.GetPlaceDetails(from.SelectedSuggestion.Reference, details =>
					{
						if (details == null)
						{
							callback(null);
							return;
						}
						Models.GoogleMaps.Location l = details.Geometry.Location;
						from.Location = new Location { Latitude = l.Lat, Longitude = l.Lng };
						if (to.Location == null)
						{
							return;
						}
						GetDirections(from.Location, to.Location, mapView, travelMode, callback);
						return;
					});
				}
			}
			if (to.Location == null)
			{
				if (to.SelectedSuggestion == null)
				{
					GetLocations(to.Input, mapView, 0, locations =>
					{
						if (locations == null || locations.Count == 0)
						{
							callback(null);
							return;
						}
						to.Location = locations[0];
						if (from.Location == null)
						{
							return;
						}
						GetDirections(from.Location, to.Location, mapView, travelMode, callback);
						return;
					});
				}
				else
				{
					GoogleMapsServices.GetPlaceDetails(to.SelectedSuggestion.Reference, details =>
					{
						if (details == null)
						{
							callback(null);
							return;
						}
						Models.GoogleMaps.Location l = details.Geometry.Location;
						to.Location = new Location { Latitude = l.Lat, Longitude = l.Lng };
						if (from.Location == null)
						{
							return;
						}
						GetDirections(from.Location, to.Location, mapView, travelMode, callback);
					});
				}
			}
		}

		private static void GetLocations(string address, ShapeBase mapView, int resultSize, Action<ObservableCollection<Location>> callback)
		{
			var c = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
			c.GeocodeCompleted += (s, e) =>
			{
				ObservableCollection<Location> locations = null;
				try
				{
					if (e.Result == null)
					{
						return;
					}
					locations = new ObservableCollection<Location>();
					foreach (var l in e.Result.Results.SelectMany(r => r.Locations))
					{
						locations.Add(l);
						if (resultSize > 0 && locations.Count == resultSize)
						{
							return;
						}
					}
				}
				catch
				{
					locations = null;
				}
				finally
				{
					callback(locations);
				}
			};
			_geocodeRequest.Query = address;
			_geocodeRequest.UserProfile.MapView = mapView;
			if (ViewModelLocator.MainStatic.CurrentLocation == null || ViewModelLocator.MainStatic.CurrentLocation.IsUnknown)
			{
				_geocodeRequest.UserProfile.CurrentLocation = null;
			}
			else
			{
				_geocodeUserLocation.Latitude = ViewModelLocator.MainStatic.CurrentLocation.Latitude;
				_geocodeUserLocation.Longitude = ViewModelLocator.MainStatic.CurrentLocation.Longitude;
				_geocodeRequest.UserProfile.CurrentLocation = _geocodeUserLocation;
			}
			c.GeocodeAsync(_geocodeRequest);
		}

		private static void GetDirections(Location from, Location to, ShapeBase mapView, TravelMode travelMode, Action<RouteResult> callback)
		{
			var c = new RouteServiceClient("BasicHttpBinding_IRouteService");
			c.CalculateRouteCompleted += (s, e) =>
			{
				RouteResult routeResult = null;
				try
				{
					routeResult = e.Result.Result;
				}
				catch
				{
					routeResult = null;
				}
				finally
				{
					callback(routeResult);
				}
			};
			Location l = _routeRequest.Waypoints[0].Location;
			l.Latitude = from.Latitude;
			l.Longitude = from.Longitude;
			l = _routeRequest.Waypoints[1].Location;
			l.Latitude = to.Latitude;
			l.Longitude = to.Longitude;
			_routeRequest.UserProfile.MapView = mapView;
			if (ViewModelLocator.MainStatic.CurrentLocation == null || ViewModelLocator.MainStatic.CurrentLocation.IsUnknown)
			{
				_routeRequest.UserProfile.CurrentLocation = null;
			}
			else
			{
				_routeUserLocation.Latitude = ViewModelLocator.MainStatic.CurrentLocation.Latitude;
				_routeUserLocation.Longitude = ViewModelLocator.MainStatic.CurrentLocation.Longitude;
				_routeRequest.UserProfile.CurrentLocation = _routeUserLocation;
			}
			switch (ViewModelLocator.MainStatic.Settings.DistanceUnit)
			{
				case SettingsViewModel.UnitKey.Auto:
					_routeRequest.UserProfile.DistanceUnit = RegionInfo.CurrentRegion.IsMetric ? BingMaps.Route.DistanceUnit.Kilometer : BingMaps.Route.DistanceUnit.Mile;
					break;
				case SettingsViewModel.UnitKey.Metric:
					_routeRequest.UserProfile.DistanceUnit = BingMaps.Route.DistanceUnit.Kilometer;
					break;
				case SettingsViewModel.UnitKey.Imperial:
					_routeRequest.UserProfile.DistanceUnit = BingMaps.Route.DistanceUnit.Mile;
					break;
			}
			_routeRequest.Options.Mode = travelMode;
			c.CalculateRouteAsync(_routeRequest);
		}

		public static void GetMapUri(Location location, Action<MapUriResponse> callback)
		{
			var c = new ImageryServiceClient("BasicHttpBinding_IImageryService");
			c.GetMapUriCompleted += (s, e) =>
			{
				MapUriResponse response = null;
				try
				{
					response = e.Result;
				}
				catch
				{
					response = null;
				}
				finally
				{
					callback(response);
				}
			};
			_mapUriRequest.Center.Latitude = location.Latitude;
			_mapUriRequest.Center.Longitude = location.Longitude;
			if (ViewModelLocator.MainStatic.CurrentLocation == null || ViewModelLocator.MainStatic.CurrentLocation.IsUnknown)
			{
				_mapUriRequest.UserProfile.CurrentLocation = null;
			}
			else
			{
				_mapUriRequest.UserProfile.CurrentLocation = new BingMaps.Imagery.UserLocation
				{
					Latitude = ViewModelLocator.MainStatic.CurrentLocation.Latitude,
					Longitude = ViewModelLocator.MainStatic.CurrentLocation.Longitude
				};
			}
			c.GetMapUriAsync(_mapUriRequest);
		}

		public static void GetAddress(Location location, Action<GeocodeResult> callback)
		{
			GeocodeResult geoCode = null;
			var c = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
			c.ReverseGeocodeCompleted += (s, e) =>
			{
				try
				{
					if (!e.Cancelled && e.Error == null && e.Result.Results.Count > 0)
					{
						geoCode = e.Result.Results[0];
					}
				}
				catch
				{
					geoCode = null;
				}
				finally
				{
					callback(geoCode);
				}
			};
			_reverseGeocodeRequest.Location.Latitude = location.Latitude;
			_reverseGeocodeRequest.Location.Longitude = location.Longitude;
			if (ViewModelLocator.MainStatic.CurrentLocation == null || ViewModelLocator.MainStatic.CurrentLocation.IsUnknown)
			{
				_reverseGeocodeRequest.UserProfile.CurrentLocation = null;
			}
			else
			{
				_reverseGeocodeRequest.UserProfile.CurrentLocation = new BingMaps.Geocode.UserLocation
				{
					Latitude = ViewModelLocator.MainStatic.CurrentLocation.Latitude,
					Longitude = ViewModelLocator.MainStatic.CurrentLocation.Longitude
				};
			}
			c.ReverseGeocodeAsync(_reverseGeocodeRequest);
		}
	}
}
